import { Container } from "inversify";
import { instance, mock } from 'ts-mockito';
import { CodeActionKind, debug, DebugConfigurationProvider, Disposable, languages, Memento, OutputChannel, window } from "vscode";
import { WorkspaceService } from "./common/application/workspace";
import { Commands, EQ, STANDARD_OUTPUT_CHANNEL, TEST_OUTPUT_CHANNEL, UseProposedApi } from "./common/constants";
import { GLOBAL_MEMENTO, IConfigurationService, IDisposableRegistry, IExtensionContext, IExtensions, IMemento, IOutputChannel, WORKSPACE_MEMENTO } from "./common/types";
import { OutputChannelNames } from "./common/utils/localize";
import { ActivationResult, ExtensionState } from "./components";
import { ServiceContainer } from "./ioc/container";
import { ServiceManager } from "./ioc/serviceManager";
import { IServiceContainer, IServiceManager } from "./ioc/types";
import { registerLogger, setLoggingLevel } from "./logging";
import { OutputChannelLogger } from "./logging/outputChannelLogger";
import { setExtensionInstallTelemetryProperties } from './telemetry/extensionInstallTelemetry';
import { registerTypes as commonRegisterTypes } from './common/serviceRegistry';
import { registerTypes as variableRegisterTypes } from './common/variables/serviceRegistry';
import { registerTypes as platformRegisterTypes } from './common/platform/serviceRegistry';
import { registerTypes as processRegisterTypes } from './common/process/serviceRegistry';
import { registerTypes as interpretersRegisterTypes } from './interpreter/serviceRegistry';
import { registerTypes as activationRegisterTypes } from './activation/serviceRegistry';
import { registerTypes as appRegisterTypes } from './application/serviceRegistry';
import { registerTypes as debugConfigurationRegisterTypes } from './debugger/extension/serviceRegistry';
import { registerTypes as commonRegisterTerminalTypes } from './terminals/serviceRegistry';
import { registerTypes as providersRegisterTypes } from './providers/serviceRegistry';
import { registerTypes as lintersRegisterTypes } from './linters/serviceRegistry';
import * as eqEnvironments from './eqEnvironments';
import { IDiscoveryAPI } from "./eqEnvironments/base/locator";
import { IFileSystem } from "./common/platform/types";
import { IApplicationEnvironment, ICommandManager, IWorkspaceService } from "./common/application/types";
import { IExtensionActivationManager } from "./activation/types";
import { setDefaultLanguageServer } from "./activation/common/defaultlanguageServer";
import { getLoggingLevel } from "./logging/settings";
import { IInterpreterService } from "./interpreter/contracts";
import { IDebugSessionEventHandlers } from "./debugger/extension/hook/types";
import { DebugSessionEventDispatcher } from "./debugger/extension/hook/eventHandlerDispatcher";
import { DebugService } from "./common/application/debugService";
import { noop } from "./common/utils/misc";
import { IApplicationDiagnostics } from "./application/types";
import { ICodeExecutionManager, ITerminalAutoActivation } from "./terminals/types";
import { ISortImportsEditingProvider } from "./providers/types";
import { LinterCommands } from "./linters/linterCommands";
import { DebuggerTypeName } from "./debugger/types";
import { ReplProvider } from "./providers/replProvider";
import { TerminalProvider } from "./providers/terminalProvider";
import { EQCodeActionProvider } from "./providers/codeActionProvider/eqCodeActionProvider";
import { IDebugConfigurationService, IDebuggerBanner } from "./debugger/extension/types";

export function initializeGlobals(
    // This is stored in ExtensionState.
    context: IExtensionContext,
): ExtensionState {
    const cont = new Container({ skipBaseClassChecks: true });
    const serviceManager = new ServiceManager(cont);
    const serviceContainer = new ServiceContainer(cont);
    const disposables: IDisposableRegistry = context.subscriptions;

    serviceManager.addSingletonInstance<IServiceContainer>(IServiceContainer, serviceContainer);
    serviceManager.addSingletonInstance<IServiceManager>(IServiceManager, serviceManager);

    serviceManager.addSingletonInstance<Disposable[]>(IDisposableRegistry, disposables);
    serviceManager.addSingletonInstance<Memento>(IMemento, context.globalState, GLOBAL_MEMENTO);
    serviceManager.addSingletonInstance<Memento>(IMemento, context.workspaceState, WORKSPACE_MEMENTO);
    serviceManager.addSingletonInstance<IExtensionContext>(IExtensionContext, context);

    const standardOutputChannel = window.createOutputChannel(OutputChannelNames.eq);
    context.subscriptions.push(registerLogger(new OutputChannelLogger(standardOutputChannel)));

    const workspaceService = new WorkspaceService();
    const unitTestOutChannel =
        workspaceService.isVirtualWorkspace || !workspaceService.isTrusted
            ? // Do not create any test related output UI when using virtual workspaces.
            instance(mock<IOutputChannel>())
            : window.createOutputChannel(OutputChannelNames.eqTest);
    serviceManager.addSingletonInstance<OutputChannel>(IOutputChannel, standardOutputChannel, STANDARD_OUTPUT_CHANNEL);
    serviceManager.addSingletonInstance<OutputChannel>(IOutputChannel, unitTestOutChannel, TEST_OUTPUT_CHANNEL);

    return {
        context,
        disposables,
        legacyIOC: { serviceManager, serviceContainer },
    };
}

/**
 * Registers standard utils like experiment and platform code which are fundamental to the extension.
 */
export function initializeStandard(ext: ExtensionState): void {
    const { serviceManager } = ext.legacyIOC;
    // Core registrations (non-feature specific).
    commonRegisterTypes(serviceManager);
    variableRegisterTypes(serviceManager);
    platformRegisterTypes(serviceManager);
    processRegisterTypes(serviceManager);

    // We will be pulling other code over from activateLegacy().
}

/**
 * The set of public APIs from initialized components.
 */
export type Components = {
    eqEnvs: IDiscoveryAPI;
};

/**
 * Initialize all components in the extension.
 */
export async function initializeComponents(ext: ExtensionState): Promise<Components> {
    const eqEnvs = await eqEnvironments.initialize(ext);

    // Other component initializers go here.
    // We will be factoring them out of activateLegacy().

    return {
        eqEnvs,
    };
}

export async function activateComponents(
    // `ext` is passed to any extra activation funcs.
    ext: ExtensionState,
    components: Components,
): Promise<ActivationResult[]> {

    const legacyActivationResult = await activateLegacy(ext);
    const workspaceService = new WorkspaceService();
    if (!workspaceService.isTrusted) {
        return [legacyActivationResult];
    }
    const promises: Promise<ActivationResult>[] = [
        // More component activations will go here
        eqEnvironments.activate(components.eqEnvs, ext),
    ];
    return Promise.all([legacyActivationResult, ...promises]);
}

async function activateLegacy(ext: ExtensionState): Promise<ActivationResult> {
    const { context, legacyIOC } = ext;
    const { serviceManager, serviceContainer } = legacyIOC;

    // // We need to setup this property before any telemetry is sent
    const fs = serviceManager.get<IFileSystem>(IFileSystem);
    await setExtensionInstallTelemetryProperties(fs);

    const applicationEnv = serviceManager.get<IApplicationEnvironment>(IApplicationEnvironment);
    const { enableProposedApi } = applicationEnv.packageJson;
    serviceManager.addSingletonInstance<boolean>(UseProposedApi, enableProposedApi);

    // Feature specific registrations.
    lintersRegisterTypes(serviceManager);
    interpretersRegisterTypes(serviceManager);
    commonRegisterTerminalTypes(serviceManager);
    debugConfigurationRegisterTypes(serviceManager);

    const extensions = serviceContainer.get<IExtensions>(IExtensions);
    await setDefaultLanguageServer(extensions, serviceManager);
    // Note we should not trigger any extension related code which logs, until we have set logging level. So we cannot
    // use configurations service to get level setting. Instead, we use Workspace service to query for setting as it
    // directly queries VSCode API.
    setLoggingLevel(getLoggingLevel());

    const configuration = serviceManager.get<IConfigurationService>(IConfigurationService);
    serviceContainer.get<IConfigurationService>(IConfigurationService).getSettings().initialize();

    // Language feature registrations.
    appRegisterTypes(serviceManager);
    providersRegisterTypes(serviceManager);
    activationRegisterTypes(serviceManager);
    // "initialize" "services"
    const disposables = serviceManager.get<IDisposableRegistry>(IDisposableRegistry);
    const workspaceService = serviceContainer.get<IWorkspaceService>(IWorkspaceService);
    const cmdManager = serviceContainer.get<ICommandManager>(ICommandManager);
    //languages.setLanguageConfiguration(EQ_LANGUAGE, getLanguageConfiguration());
    if (workspaceService.isTrusted) {
        const interpreterManager = serviceContainer.get<IInterpreterService>(IInterpreterService);
        interpreterManager.initialize();
        if (!workspaceService.isVirtualWorkspace) {
            const handlers = serviceManager.getAll<IDebugSessionEventHandlers>(IDebugSessionEventHandlers);
            const dispatcher = new DebugSessionEventDispatcher(handlers, DebugService.instance, disposables);
            dispatcher.registerEventHandlers();

            const outputChannel = serviceManager.get<OutputChannel>(IOutputChannel, STANDARD_OUTPUT_CHANNEL);
            disposables.push(cmdManager.registerCommand(Commands.ViewOutput, () => outputChannel.show()));
            cmdManager.executeCommand('setContext', 'eq.vscode.channel', applicationEnv.channel).then(noop, noop);

            serviceContainer.get<IApplicationDiagnostics>(IApplicationDiagnostics).register();

            serviceManager.get<ITerminalAutoActivation>(ITerminalAutoActivation).register();
            const eqSettings = configuration.getSettings();
            const sortImports = serviceContainer.get<ISortImportsEditingProvider>(ISortImportsEditingProvider);
            sortImports.registerCommands();

            serviceManager.get<ICodeExecutionManager>(ICodeExecutionManager).registerCommands();

            context.subscriptions.push(new LinterCommands(serviceManager));
            if (
                eqSettings &&
                eqSettings.formatting &&
                eqSettings.formatting.provider !== 'internalConsole'
            ) {
                // const formatProvider = new PythonFormattingEditProvider(context, serviceContainer);
                // context.subscriptions.push(languages.registerDocumentFormattingEditProvider(PYTHON, formatProvider));
                // context.subscriptions.push(
                //     languages.registerDocumentRangeFormattingEditProvider(PYTHON, formatProvider),
                // );
            }
            context.subscriptions.push(new ReplProvider(serviceContainer));

            const terminalProvider = new TerminalProvider(serviceContainer);
            terminalProvider.initialize(window.activeTerminal).ignoreErrors();
            context.subscriptions.push(terminalProvider);

            context.subscriptions.push(
                languages.registerCodeActionsProvider(EQ, new EQCodeActionProvider(), {
                    providedCodeActionKinds: [CodeActionKind.SourceOrganizeImports],
                }),
            );

            serviceContainer
                .getAll<DebugConfigurationProvider>(IDebugConfigurationService)
                .forEach((debugConfigProvider) => {
                    context.subscriptions.push(
                        debug.registerDebugConfigurationProvider(DebuggerTypeName, debugConfigProvider),
                    );
                });

            serviceContainer.get<IDebuggerBanner>(IDebuggerBanner).initialize();
        }
    }
    const manager = serviceContainer.get<IExtensionActivationManager>(IExtensionActivationManager);
    context.subscriptions.push(manager);

    const activationPromise = manager.activate();
    return { fullyReady: activationPromise };
}